﻿
    // Written by Dustin Campbell, September 20, 2007
    // dustin@diditwith.net
    //
    // Enjoy!

    using System;
    using System.Collections.Generic;

    namespace PartialApplication
    {
        public static class HigherOrder
        {
            public static IEnumerable<TSource> Filter<TSource>(Func<TSource, bool> predicate, IEnumerable<TSource> source)
            {
                if (source == null)
                    yield break;

                if (predicate == null)
                    throw new ArgumentNullException("predicate");

                foreach (TSource item in source)
                    if (predicate(item))
                        yield return item;
            }

            public static IEnumerable<TResult> Map<TSource, TResult>(Func<TSource, TResult> mapper, IEnumerable<TSource> source)
            {
                if (source == null)
                    yield break;

                if (mapper == null)
                    throw new ArgumentNullException("mapper");

                foreach (TSource item in source)
                    yield return mapper(item);
            }

            public static TResult Reduce<TSource, TResult>(Func<TSource, TResult, TResult> accumulator, TResult startValue, IEnumerable<TSource> source)
            {
                if (source == null)
                    return startValue;

                if (accumulator == null)
                    throw new ArgumentNullException("accumulator");

                TResult result = startValue;
                foreach (TSource item in source)
                    result = accumulator(item, result);

                return result;
            }

            public static IEnumerable<TSource> Sequence<TSource>(Func<TSource, TSource> getNext, TSource startValue, Func<TSource, bool> shouldStop)
            {
                if (getNext == null)
                    yield break;

                TSource result = startValue;
                yield return result;

                while (shouldStop != null && !shouldStop(result))
                {
                    result = getNext(result);
                    yield return result;
                }
            }

            public static Func<Func<TSource, bool>, IEnumerable<TSource>, IEnumerable<TSource>> GetFilter<TSource>()
            {
                return Filter<TSource>;
            }
            public static Func<Func<TSource, TResult>, IEnumerable<TSource>, IEnumerable<TResult>> GetMap<TSource, TResult>()
            {
                return Map<TSource, TResult>;
            }
            public static Func<Func<TSource, TResult, TResult>, TResult, IEnumerable<TSource>, TResult> GetReduce<TSource, TResult>()
            {
                return Reduce<TSource, TResult>;
            }
            public static Func<Func<TSource, TSource, TSource>, TSource, IEnumerable<TSource>, TSource> GetReduce<TSource>()
            {
                return Reduce<TSource, TSource>;
            }
            public static Func<Func<TSource, TSource>, TSource, Func<TSource, bool>, IEnumerable<TSource>> GetSequence<TSource>()
            {
                return Sequence<TSource>;
            }

            public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult> func)
            {
                if (func == null)
                    throw new ArgumentNullException("func");

                return arg1 => arg2 => func(arg1, arg2);
            }
            public static Func<T1, Func<T2, Func<T3, TResult>>> Curry<T1, T2, T3, TResult>(this Func<T1, T2, T3, TResult> func)
            {
                if (func == null)
                    throw new ArgumentNullException("func");

                return arg1 => arg2 => arg3 => func(arg1, arg2, arg3);
            }
            public static Func<T1, Func<T2, Func<T3, Func<T4, TResult>>>> Curry<T1, T2, T3, T4, TResult>(this Func<T1, T2, T3, T4, TResult> func)
            {
                if (func == null)
                    throw new ArgumentNullException("func");

                return arg1 => arg2 => arg3 => arg4 => func(arg1, arg2, arg3, arg4);
            }

            public static Func<T1, T2, TResult> Uncurry<T1, T2, TResult>(this Func<T1, Func<T2, TResult>> func)
            {
                if (func == null)
                    throw new ArgumentNullException("func");

                return (arg1, arg2) => func(arg1)(arg2);
            }
            public static Func<T1, T2, T3, TResult> Uncurry<T1, T2, T3, TResult>(this Func<T1, Func<T2, Func<T3, TResult>>> func)
            {
                if (func == null)
                    throw new ArgumentNullException("func");

                return (arg1, arg2, arg3) => func(arg1)(arg2)(arg3);
            }
            public static Func<T1, T2, T3, T4, TResult> Uncurry<T1, T2, T3, T4, TResult>(this Func<T1, Func<T2, Func<T3, Func<T4, TResult>>>> func)
            {
                if (func == null)
                    throw new ArgumentNullException("func");

                return (arg1, arg2, arg3, arg4) => func(arg1)(arg2)(arg3)(arg4);
            }
        }
    }
